package com.heima.schedule.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.common.redis.CacheService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.dtos.TaskDto;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.common.pojos.schedule.Taskinfo;
import com.heima.model.common.pojos.schedule.TaskinfoLogs;
import com.heima.schedule.mapper.TaskinfoLogsMapper;
import com.heima.schedule.mapper.TaskinfoMapper;
import com.heima.schedule.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * @author 陈辉
 * @data 2024 9:37
 */
@Service
@Slf4j
public class TaskServiceImpl implements TaskService {
    @Autowired
    private TaskinfoMapper taskinfoMapper;
    @Autowired
    private TaskinfoLogsMapper taskinfoLogsMapper;
    @Autowired
    private CacheService cacheService;
    /**
     * 添加任务
     *
     * @param taskDto
     * @return
     */
    @Override
    public ResponseResult addTask(TaskDto taskDto) {
        //1. 先将任务添加到DB
        addTaskToDB(taskDto);
        //2. 再根据任务的时间来决定要不要将任务添加到Redis,以及是添加到List还是ZSet
        addTaskToRedis(taskDto);
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    //添加任务到Redis
    private void addTaskToRedis(TaskDto taskDto) {
        //1. 拿到几个时间
        //拿到任务的执行时间
        long executeTime = taskDto.getExecuteTime();
        //拿到当前系统时间
        long now = System.currentTimeMillis();
        //拿到预加载时间：当前系统时间 + 5分钟
        long futureTime = now + (1000L * 60 * 5);

        //2. 任务执行时间 <= 当前系统时间   : 将任务添加到List
        if (executeTime <= now){
            cacheService.lLeftPush("topic_"+ taskDto.getTaskType()+"_"+taskDto.getPriority(), JSON.toJSONString(taskDto));
        }else if (executeTime <= futureTime){
            //3. 任务执行时间 > 当前系统时间  && <= 预加载时间  : 将任务添加到ZSet中
            cacheService.zAdd("future_"+ taskDto.getTaskType()+"_"+taskDto.getPriority(), JSON.toJSONString(taskDto),executeTime);
        }

    }

    //添加任务到数据库
    private void addTaskToDB(TaskDto taskDto) {
        //先保存任务数据到taskinfo
        Taskinfo taskinfo = new Taskinfo();
        BeanUtils.copyProperties(taskDto,taskinfo);
        taskinfo.setExecuteTime(new Date(taskDto.getExecuteTime()));
        taskinfoMapper.insert(taskinfo);
        //将任务id回填到dto上，便于后期任务删除
        taskDto.setTaskId(taskinfo.getTaskId());

        //再保存任务数据到taskinfo_logs
        TaskinfoLogs logs = new TaskinfoLogs();
        BeanUtils.copyProperties(taskinfo,logs);
        logs.setVersion(1);
        logs.setStatus(0);
        taskinfoLogsMapper.insert(logs);
    }

    /**
     * 拉取任务
     *
     * @param taskType
     * @param priority
     * @return
     */
    @Override
    public ResponseResult pullTask(Integer taskType, Integer priority) {
        //1. 根据任务类型和优先级去redis的List队列中取出对应key的任务数据
        String key = "topic_"+ taskType + "_"+ priority;
        String taskDtoJSON = cacheService.lRightPop(key);

        if (StringUtils.isNotEmpty(taskDtoJSON)) {
            //2.1 将当前任务对应的DB中的Taskinfo表中记录删除掉
            TaskDto taskDto = JSON.parseObject(taskDtoJSON, TaskDto.class);
            taskinfoMapper.deleteById(taskDto.getTaskId());

            //2.2 将当前任务对应的DB中的TaskinfoLogs表中状态修改为：1：已执行
            TaskinfoLogs logs = new TaskinfoLogs();
            logs.setTaskId(taskDto.getTaskId());
            logs.setStatus(1);      //1=EXECUTED
            taskinfoLogsMapper.updateById(logs);

            //3. 将当前任务信息返回
            return ResponseResult.okResult(taskDtoJSON);
        }else {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
    }

    //定时刷新ZSet数据到List中, 任务执行频次：每1分钟执行一次!
    @Scheduled(cron = "0 0/1 * * * ? ")
    public void refreshZSetToList(){
        //查询所有ZSet的key
        Set<String> zsetKeys = cacheService.scan("future_*");
        if (zsetKeys != null && zsetKeys.size() > 0){
            log.info("---------------定时刷新ZSet数据到List中------------------");
            //筛选出ZSet中任务执行时间 <= 当前系统时间的任务数据
            for (String zsetKey : zsetKeys) {
                Set<String> taskDtos = cacheService.zRangeByScore(zsetKey, 0, System.currentTimeMillis());

                //将这批到期的任务由ZSet集合转移到List集合
                String[] split = zsetKey.split("future_");
                String listKey = "topic_"+split[1];

                //将已经时间到期的这批任务：taskDtos从zsetKey对应的集合中移除，并且添加到listKey对应的集合中
                cacheService.refreshWithPipeline(zsetKey,listKey,taskDtos);
            }
        }
    }

    //定时刷新DB数据到Redis中, 任务执行频次：每5分钟执行一次!
    @Scheduled(cron = "0 0/5 * * * ? ")
    public void refreshDBToRedis(){
        //查询出数据库中 执行时间 <= 预设时间 的任务数据都查出来
        Date futureTime = new Date(System.currentTimeMillis() + (1000L * 60 * 5));
        List<Taskinfo> taskinfoList = taskinfoMapper.selectList(Wrappers.<Taskinfo>lambdaQuery().le(Taskinfo::getExecuteTime, futureTime));

        if (taskinfoList != null && taskinfoList.size() > 0){
            log.info("---------------定时刷新DB数据到Redis中------------------");
            //遍历集合：拿到每一个任务
            for (Taskinfo taskinfo : taskinfoList) {
                //添加任务到Redis
                TaskDto dto = new TaskDto();
                BeanUtils.copyProperties(taskinfo,dto);
                dto.setExecuteTime(taskinfo.getExecuteTime().getTime());
                addTaskToRedis(dto);
            }
        }
    }
}
